//
//  WFLocationManager.m
//  WisdomFarm
//
//  Created by sky on 2017/10/31.
//  Copyright © 2017年 sky. All rights reserved.
//

#import "WFLocationManager.h"
#import "WFStore.h"

typedef NS_ENUM(NSUInteger, WFLocationManagerStatus) {
    WFLocationManagerStatusDefault,
    WFLocationManagerStatusLocating,
    WFLocationManagerStatusLocatingForAddress,
};

@interface WFLocationManager ()<
CLLocationManagerDelegate
>

@property (nonatomic, strong) CLLocationManager *manager;
@property (nonatomic, strong) NSMutableSet *blocks;
@property (nonatomic, strong) NSMutableSet *addressBlocks;
@property (nonatomic, assign) WFLocationManagerStatus status;
@property (nonatomic, assign) BOOL isForAddress;

@end

@implementation WFLocationManager

+ (instancetype)sharedManager {
    static dispatch_once_t onceToken;
    static WFLocationManager *manager = nil;
    dispatch_once(&onceToken, ^{
        manager = [WFLocationManager new];
    });
    return manager;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.manager = [[CLLocationManager alloc] init];
        self.manager.delegate = self;
        [self.manager requestWhenInUseAuthorization];
    }
    return self;
}

- (NSMutableSet *)blocks {
    if (!_blocks) {
        _blocks = [NSMutableSet set];
    }
    return _blocks;
}

- (NSMutableSet *)addressBlocks {
    if (!_addressBlocks) {
        _addressBlocks = [NSMutableSet set];
    }
    return _addressBlocks;
}

- (void)startUpdateLocation:(WFLocationManagerHandle)completion {
    CLLocation *location = [WFCacheStore sharedCacheStore].currentLocation;
    if (location) {
        if (completion) {
            completion(location);
        }
        return;
    }
    if (completion) {
        [self.blocks addObject:completion];
    }
    if (self.status == WFLocationManagerStatusDefault) {
        self.status = WFLocationManagerStatusLocating;
        [self.manager startUpdatingLocation];
    }
}

- (void)startUpdateLocationForAddress:(WFLocationManagerAddressHandle)completion {
    NSString *address = [WFCacheStore sharedCacheStore].currentAddress;
    if (address) {
        if (completion) {
            completion(address);
        }
        return;
    }
    if (completion) {
        [self.addressBlocks addObject:completion];
    }
    self.isForAddress = YES;
    if (self.status == WFLocationManagerStatusDefault) {
        self.status = WFLocationManagerStatusLocating;
        [self.manager startUpdatingLocation];
    }
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
    [self.manager stopUpdatingLocation];
    if (self.status != WFLocationManagerStatusLocating) {
        return;
    }
    //fail
    if (locations.count == 0) {
        self.status = WFLocationManagerStatusDefault;
        [self notifyLocationFailure];
        [self notifyAddressFailure];
        return;
    }
    //location
    CLLocation *location = [locations firstObject];
    CLLocationCoordinate2D GCJ = [[self class] GCJFromWGS:location.coordinate];
    location = [[CLLocation alloc] initWithLatitude:GCJ.latitude longitude:GCJ.longitude];
    [WFCacheStore sharedCacheStore].currentLocation = location;
    if (!self.isForAddress) {
        self.status = WFLocationManagerStatusDefault;
        [self notifyLocationSuccess:location];
        return;
    }
    //address
    self.status = WFLocationManagerStatusLocatingForAddress;
    [WFLocationManager requestAddressByLocation:location complete:^(NSString *address) {
        if (!address) {
            [self notifyAddressFailure];
        } else {
            [WFCacheStore sharedCacheStore].currentAddress = address;
            [self notifyAddressSuccess:address];
        }
        self.status = WFLocationManagerStatusDefault;
    }];
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
    [self.manager stopUpdatingLocation];
    if (self.status == WFLocationManagerStatusLocating || self.status == WFLocationManagerStatusLocatingForAddress) {
        self.status = WFLocationManagerStatusDefault;
        [self notifyLocationFailure];
        [self notifyAddressFailure];
    }
}

- (void)notifyLocationFailure {
    NSSet *blocks = [self.blocks copy];
    for (WFLocationManagerHandle handle in blocks) {
        handle(nil);
    }
    [self.blocks removeAllObjects];
}

- (void)notifyAddressFailure {
    NSSet *addressBlocks = [self.addressBlocks copy];
    for (WFLocationManagerAddressHandle handle in addressBlocks) {
        handle(nil);
    }
    [self.addressBlocks removeAllObjects];
}

- (void)notifyLocationSuccess:(CLLocation *)location {
    NSSet *blocks = [self.blocks copy];
    for (WFLocationManagerHandle handle in blocks) {
        handle(location);
    }
    [self.blocks removeAllObjects];
}

- (void)notifyAddressSuccess:(NSString *)address {
    NSSet *addressBlocks = [self.addressBlocks copy];
    for (WFLocationManagerAddressHandle handle in addressBlocks) {
        handle(address);
    }
    [self.addressBlocks removeAllObjects];

}

@end

const double x_pi = 3.14159265358979324 * 3000.0 / 180.0;
const double a = 6378245.0;
const double ee = 0.00669342162296594323;
const double pi = 3.14159265358979324;

@implementation WFLocationManager (Transform)

+(void)requestLocationByAddress:(NSString *)address complete:(void (^)(CLLocation *))completeHandle{
    if (!completeHandle) {
        completeHandle = ^(CLLocation *location){};
    }
    MZBaseRequest *request = [[MZBaseRequest alloc] init];
    request.path = @"http://api.map.baidu.com/geocoder/v2/";
    request.parameters = @{
                           @"address" : address,
                           @"output" : @"json",
                           @"ak" : @"Y0VyQcNLbWhFsLnbsmbKMn6FalDwrGRi",
                           @"mcode" : @"sky.WisdomFarm",
                           };
    [request startWithBlock:^(__kindof MZBaseRequest *request, NSError *error) {
        NSDictionary *responseDic = request.response.jsonObject;
        NSDictionary *resultDic = [responseDic objectForKey:@"result"];
        NSDictionary *locationDic = [resultDic objectForKey:@"location"];
        if (![locationDic sc_isNotEmpty]) {
            completeHandle(nil);
            return;
        }
        double lat=[[locationDic objectForKey:@"lat"] doubleValue];
        double lon=[[locationDic objectForKey:@"lng"] doubleValue];
        CLLocationCoordinate2D bd=CLLocationCoordinate2DMake(lat, lon);
        CLLocationCoordinate2D gcj=[self GCJFromBD:bd];
        CLLocation *location=[[CLLocation alloc] initWithLatitude:gcj.latitude longitude:gcj.longitude];
        completeHandle(location);
    }];
}

+(void)requestAddressByLocation:(CLLocation *)location complete:(void (^)(NSString *))completeHandle{
    if (!completeHandle) {
        completeHandle = ^(NSString *address){};
    }
    MZBaseRequest *request = [[MZBaseRequest alloc] init];
    request.path = @"http://api.map.baidu.com/geocoder/v2/";
    NSString *locationStr=[NSString stringWithFormat:@"%f,%f",location.coordinate.latitude,location.coordinate.longitude];
    request.parameters = @{
                           @"location" : locationStr,
                           @"output" : @"json",
                           @"ak" : @"Y0VyQcNLbWhFsLnbsmbKMn6FalDwrGRi",
                           @"coordtype" : @"gcj02ll",
                           @"mcode" : @"sky.WisdomFarm",
                           };
    [request startWithBlock:^(__kindof MZBaseRequest *request, NSError *error) {
        NSDictionary *responseDic = request.response.jsonObject;
        NSDictionary *resultDic=[responseDic objectForKey:@"result"];
        NSArray *poiRegions = resultDic[@"poiRegions"];
        NSString *address=[resultDic objectForKey:@"formatted_address"];
        if (poiRegions.count > 0) {
            NSDictionary *poiRegion = poiRegions.firstObject;
            NSString *name = poiRegion[@"name"];
            if (name.length > 0) {
                address = [address stringByAppendingString:name];
            }
        }
        completeHandle(address);
    }];
}

+(CLLocationCoordinate2D)GCJFromBD:(CLLocationCoordinate2D)coordinate{
    double bdLat=coordinate.latitude;
    double bdLon=coordinate.longitude;
    
    double x=bdLon-0.0065,y=bdLat-0.006;
    double z=sqrt(x * x + y * y) - 0.00002 * sin(y * x_pi);
    double theta = atan2(y, x) - 0.000003 * cos(x * x_pi);
    
    double lon=z*cos(theta);
    double lat=z*sin(theta);
    return CLLocationCoordinate2DMake(lat, lon);
}

+(CLLocationCoordinate2D)BDFromGCJ:(CLLocationCoordinate2D)coordinate{
    double ggLat=coordinate.latitude;
    double ggLon=coordinate.longitude;
    
    double x = ggLon, y = ggLat;
    double z = sqrt(x * x + y * y) + 0.00002 * sin(y * x_pi);
    double theta = atan2(y, x) + 0.000003 * cos(x * x_pi);
    double lon = z * cos(theta) + 0.0065;
    double lat = z * sin(theta) + 0.006;
    return CLLocationCoordinate2DMake(lat, lon);
}

+(CLLocationCoordinate2D)GCJFromWGS:(CLLocationCoordinate2D)wgsLoc{
    CLLocationCoordinate2D adjustLoc;
    double adjustLat = [self transformLatWithX:wgsLoc.longitude - 105.0 withY:wgsLoc.latitude - 35.0];
    double adjustLon = [self transformLonWithX:wgsLoc.longitude - 105.0 withY:wgsLoc.latitude - 35.0];
    double radLat = wgsLoc.latitude / 180.0 * pi;
    double magic = sin(radLat);
    magic = 1 - ee * magic * magic;
    double sqrtMagic = sqrt(magic);
    adjustLat = (adjustLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
    adjustLon = (adjustLon * 180.0) / (a / sqrtMagic * cos(radLat) * pi);
    adjustLoc.latitude = wgsLoc.latitude + adjustLat;
    adjustLoc.longitude = wgsLoc.longitude + adjustLon;
    return adjustLoc;
    
}

+(double)transformLatWithX:(double)x withY:(double)y
{
    double lat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(abs(x));
    lat += (20.0 * sin(6.0 * x * pi) + 20.0 *sin(2.0 * x * pi)) * 2.0 / 3.0;
    lat += (20.0 * sin(y * pi) + 40.0 * sin(y / 3.0 * pi)) * 2.0 / 3.0;
    lat += (160.0 * sin(y / 12.0 * pi) + 320 * sin(y * pi / 30.0)) * 2.0 / 3.0;
    return lat;
}

+(double)transformLonWithX:(double)x withY:(double)y
{
    double lon = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(abs(x));
    lon += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
    lon += (20.0 * sin(x * pi) + 40.0 * sin(x / 3.0 * pi)) * 2.0 / 3.0;
    lon += (150.0 * sin(x / 12.0 * pi) + 300.0 * sin(x / 30.0 * pi)) * 2.0 / 3.0;
    return lon;
}

@end
